import { RechargeOrder } from '@app/db/entities/RechargeOrder';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, FindOptionsWhere, LessThan, Repository } from 'typeorm';
import { WechatService } from '../wechat/wechat.service';
import { RechargeTemp, RechargeTempDocument } from '@app/mongodb/schemas/rechargeTemp';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { RechargeOption } from '@app/db/entities/RechargeOption';
import { User } from '@app/db/entities/User';
import { UserWalletLog } from '@app/db/entities/UserWalletLog';
import { CacheService } from '@app/cache';
import { AlipayService } from '../alipay/alipay.service';

@Injectable()
export class RechargeService {
    constructor(
        @InjectRepository(RechargeOrder) protected repository:Repository<RechargeOrder>,
        @InjectRepository(RechargeOption) protected rechargeOption:Repository<RechargeOption>,
        @InjectModel(RechargeTemp.name) private readonly rechargeTemp: Model<RechargeTempDocument>,
        private dataSource: DataSource,
        private readonly wechat:WechatService,
        private readonly alipay:AlipayService,
        private readonly cache:CacheService
    ) {}

    options() {
        return this.rechargeOption.find({
            select : ['id', 'amount', 'reward']
        })
    }

    private generateOrderNo(userId:number) {
        const date = new Date()
        const time = Math.floor(date.getTime() / 1000) % 1000000
        const year = date.getFullYear() % 100
        const month = String(date.getMonth() + 1).padStart(2, '0')
        const day = String(date.getDate()).padStart(2, '0')
        return year + month + day + time + String(userId).padStart(10, '0')
    }

    async recharge(id:number, userId:number, clientSource:number, isWx:boolean, openid:string) {
        const option = await this.rechargeOption.findOne({
            where : {id},
            select : ['amount', 'reward']
        })
        if(!option) return null

        let amount = Number((Number(option.amount) + Math.random() * (100 - 1) + 1).toFixed(2))
        let rewardAmount = Number(option.reward)


        let time = Math.floor(Date.now() / 1000)
        const orderNo = this.generateOrderNo(userId)
        // let order:{[key in keyof RechargeOrder]? : RechargeOrder[key]} = {
        //     orderNo : orderNo,
        //     amount : String(amount),
        //     rewardAmount : String(0),
        //     payChannel : clientSource,
        //     userId : userId,
            
        //     createTime : time
        // }


        const res = await this.rechargeTemp.insertMany({
            orderNo : orderNo,
            time : time,
            status : 0,
            data : {
                payChannel : clientSource,
                amount,
                reward : rewardAmount,
                userId,
            }
        })

        const _id = res[0]._id.toString()
        const notifyUrl = 'recharge/notify/'

        if(isWx) {
            return this.wechat.pay(amount, orderNo, openid, '充值', notifyUrl + 'wx/' + _id)
            //await this.wechat.pay(amount, , openid, '充值')
        }else {
            return this.alipay.createTrade(amount, orderNo, openid, '充值', notifyUrl + 'ali/' + _id)
        }
    }

    async notify(id, data, isWx:boolean) {
        const lockKey= 'p:n:l' + id
        const lock = await this.cache.set(lockKey, '1', 20, 'NX')
        if(!lock) return false

        const temp = await this.rechargeTemp.findById(id, {_id : 1, status : 1, orderNo : 1, time : 1, data : 1})
        if(!temp) return false
        if(temp.status == 1) return true



        let tradeNo = ''

        if(isWx) {
            try {
                const res = await this.wechat.decipher_gcm(data.ciphertext, data.associated_data, data.nonce)

                if(res.trade_state != 'SUCCESS' || res.out_trade_no != temp.orderNo || res.amount.total != (temp.data.amount * 100)) return false

                tradeNo = res.transaction_id

            } catch (error) {
                this.cache.del(lockKey)
                console.log(error)
                return false
            } 
           
        }else { //支付宝
            try {
                if(!this.alipay.checkNotifySign(data)) return false

                if(data.out_trade_no != temp.orderNo || data.total_amount != temp.data.amount) return false

                tradeNo = data.trade_no

            } catch (error) {
                this.cache.del(lockKey)
                console.log(error)
                return false
            }
        }

        const time = Math.floor(Date.now() / 1000)
        const userId = temp.data.userId

        const order:{[key in keyof RechargeOrder]? : RechargeOrder[key]} = {
            orderNo : temp.orderNo,
            userId : userId,
            payChannel : temp.data.payChannel,
            amount : String(temp.data.amount),
            tradeNo : tradeNo,
            createTime : time
        }

        if(temp.data.reward > 0) {
            order.rewardAmount = String(temp.data.reward)
        }


        const queryRunner = await this.dataSource.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {
            let user = await queryRunner.manager.findOne<User>(User, {
                where : {id : userId},
                select : ['amount', 'rewardAmount'],
                lock : {
                    mode : 'pessimistic_read'
                }
            })


            //用户钱包更新
            let updateData:{amount:string, rewardAmount?:string} = {
                amount : (Number(user.amount) + temp.data.amount).toFixed(2)
            }

            //钱包记录
            const log:{[key in keyof UserWalletLog]? : UserWalletLog[key]} = {
                amount : order.amount,
                beforeAmount : user.amount,
                beforeRewardAmount : user.rewardAmount,
                afterAmount : updateData.amount,

                userId,
                createTime : time,
                type : 1
            }
            
            if(temp.data.reward > 0) {
                log.rewardAmount = order.rewardAmount
                log.afterRewardAmount = updateData.rewardAmount = (Number(user.rewardAmount) + temp.data.reward).toFixed(2)
                
            }
            
            //更改用户余额
            const r1 = await queryRunner.manager.update<User>(User, { id : userId }, updateData)
            if(r1.affected < 1) {
                await queryRunner.rollbackTransaction();
                return false
            }

            

            //加入充值订单
            const r2 = await queryRunner.manager.insert<RechargeOrder>(RechargeOrder, order)
            if(!r2.raw || r2.raw.affectedRows < 1) {
                await queryRunner.rollbackTransaction();
                return false
            }
            log.relationId = r2.raw.insertId

            //加入钱包记录
            const r3 = await queryRunner.manager.insert<UserWalletLog>(UserWalletLog, log)
            if(!r3.raw || r3.raw.affectedRows < 1) {
                await queryRunner.rollbackTransaction();
                return false
            }

            const r4 = await this.rechargeTemp.updateOne({_id : temp._id}, {status : 1})
            if(r4.modifiedCount != 1) {
                await queryRunner.rollbackTransaction();
                return false
            }

            await queryRunner.commitTransaction()

            return true
            
        } catch (error) {
            console.log(error)
            await queryRunner.rollbackTransaction();

            return false
        } finally {
            this.cache.del(lockKey)
            await queryRunner.release();
        }
        
    }

    async page(query:Pagination, userId:number) {
        let where:FindOptionsWhere<RechargeOrder> = { userId }
        const lastId = query.lastKey || 0
        const pageSize = query.pageSize || 20

        if(lastId > 0) {
            where.id = LessThan(lastId)
        }

        let rows:RechargeOrder[] = await this.repository.find({
            where,
            select : ['id', 'orderNo', 'amount', 'rewardAmount', 'createTime'], 
            skip : 0, 
            take : pageSize, 
            order : {id : 'DESC'}
        })

        return rows
    }
}
